package sk.naive.talker.server;

import sk.naive.talker.*;
import sk.naive.talker.props.PropertyStoreException;
import sk.naive.talker.persistence.*;
import sk.naive.talker.util.*;

import java.util.*;
import java.util.logging.Level;
import java.lang.reflect.*;
import java.rmi.RemoteException;

/**
 * ThingRegistry.
 *
 * @author <a href="mailto:sue@naive.deepblue.sk">Sue</a>
 * @version $Revision: 1.6 $ $Date: 2007-05-07 10:34:20 $
 */
public class ThingRegistry extends PersistentObjectFactory implements ThingFinder {
	private Map<String, Map<String, Thing>> typeNameThing;
	private Map<Integer, Thing> idThing;
	private Map<String, Class<Thing>> typeClass;
	private Map<String, PropertyConfiguration> typePropertyConfiguration;

	//	private Map<User,Thing> userThing;
	private Talker talker;

	public ThingRegistry(String propertiesBundleName, Talker talker) throws PersistenceException {
		super(propertiesBundleName);
		this.talker = talker;
		typeNameThing = new HashMap<String, Map<String, Thing>>();
		typeNameThing.put("salko", new HashMap<String, Thing>());
		typeNameThing.put("margot", new HashMap<String, Thing>());
		configureThingTypes();
		idThing = new HashMap<Integer, Thing>();
		for (Integer id : objectIdList()) {
			Thing thing = getPersistentObject(id);
			if (thing != null) {
				if (thing.getType() != null) {
					addThing(thing);
				} else {
					Utils.warning("Deleting untyped thing with id=" + thing.getId() + ".");
					remove(thing);
				}
			}
		}
	}

	private static final String CONFIG_THING_CLASS_SUFFIX = ".class";

	private void configureThingTypes() {
		typeClass = new HashMap<String, Class<Thing>>();
		typePropertyConfiguration = new HashMap<String, PropertyConfiguration>();

		Configuration config = Main.getConfiguration();
		for (String key : config.getSectionProps("things")) {
			if (key.endsWith(CONFIG_THING_CLASS_SUFFIX)) {
				String type = key.substring(0, key.length() - CONFIG_THING_CLASS_SUFFIX.length());
				String thingClassName = config.get(key);
				try {
					typeClass.put(type, (Class<Thing>) Class.forName(thingClassName));
				} catch (ClassNotFoundException e) {
					talker.getLogger().log(Level.WARNING, "Problem with thing type class '" + thingClassName + "'.");
				}
				try {
					typePropertyConfiguration.put(type, new PropertyConfiguration(thingClassName + "PropertyResources"));
				} catch (MissingResourceException e) {
					Utils.unexpectedExceptionWarning(e);
				}
			}
		}
	}

	public Thing getThing(String name) throws PersistenceException {
		return (Thing) getPersistentObjectByName(name);
	}

	public Thing getPersistentObject(Integer id) throws PersistenceException {
		DbLayer dbLayer = new DbLayer();
		String thingType = dbLayer.loadProperty(id, Thing.TPROP_TYPE);
		Class<Thing> thingClass = typeClass.get(thingType);
		if (thingClass == null) {
			talker.getLogger().log(Level.WARNING, "Trying to create thing of type '" + thingType + "' - no class defined.");
			return null;
		}
		Thing thing = null;
		try {
			PropertyConfiguration propertyConfiguration = typePropertyConfiguration.get(thingType);
			if (propertyConfiguration == null) {
				propertyConfiguration = getPropertyConfiguration();
			}
			Constructor constructor = thingClass.getConstructor(new Class[]{String.class, PropertyConfiguration.class});
			thing = (Thing) constructor.newInstance(id, propertyConfiguration);
			dbLayer.loadProperties(thing);
		} catch (NoSuchMethodException e) {
			Utils.unexpectedExceptionWarning(e);
		} catch (IllegalAccessException e) {
			Utils.unexpectedExceptionWarning(e);
		} catch (InvocationTargetException e) {
			Utils.unexpectedExceptionWarning(e);
		} catch (InstantiationException e) {
			Utils.unexpectedExceptionWarning(e);
		}
		return thing;
	}

	/**
	 * Unused for things - this method is used in getPersistentObject, which is
	 * completly overriden in this factory.
	 */
	protected Thing persistentObjectInstance(Integer id) throws PersistenceException {
		throw new UnsupportedOperationException("Things are obtaind only via overriden getPersistentObject method!");
	}

	protected int getType() {
		return DbLayer.OBJECT_TYPE_THING;
	}

	protected String getNamingProperty() {
		return Thing.TPROP_NAME;
	}

	public ThingFinder finder() {
		return this;
	}

	public Thing createThing(String type, String name) throws PersistenceException, PropertyStoreException {
		name = Utils.normalize(name);
		Thing thing = null;
		Map<String, Thing> nameThing = typeNameThing.get(type);
		if (nameThing != null && !nameThing.containsKey(name)) {
			thing = (Thing) create(name);
			thing.set(Thing.TPROP_TYPE, type);
			addThing(thing);
		}
		return thing;
	}

	public void destroy(Thing thing) throws PersistenceException, RemoteException {
		takeThingFromUsers(thing);
		remove(thing);
		removeThing(thing);
	}

	private void takeThingFromUsers(Thing thing) throws RemoteException {
//		for (Iterator i = thing.users().iterator(); i.hasNext(); ) {
//			User u = (User) i.next();
//			u.send(talker.messageFactory().getString("server.currentThingDestroyed", u.getProperties()));
//			i.remove();
//			thing.userOut(u);
//		}
	}

	private void removeThing(Thing thing) {
		typeNameThing.get(thing.getType()).remove(thing.getName());
		idThing.remove(thing.getId());
	}

	private void addThing(Thing thing) {
		typeNameThing.get(thing.getType()).put(thing.getName(), thing);
		idThing.put(thing.getId(), thing);
	}

	public Thing findThingById(Integer id) {
		return idThing.get(id);
	}

	public Thing findThingByTypeAndName(String type, String name) {
		name = Utils.normalize(name);
		if (name == null) {
			return null;
		}
		Map<String, Thing> nameThing = typeNameThing.get(type);
		if (nameThing == null) {
			return null;
		}
		return nameThing.get(name);
	}

	public Thing findThingForUser(User user) {
		return null;
//		return (Thing) userThing.get(user);
	}

//	public void userToThing(User user, Thing loc) {
//		if (loc != null) {
//			userThing.put(user, loc);
//		} else {
//			userThing.remove(user);
//		}
//	}

	public Collection<Thing> things(String type) {
		if (type == null) {
			return idThing.values();
		} else {
			return typeNameThing.get(type).values();
		}
	}

	public Collection<String> thingTypes() {
		return typeNameThing.keySet();
	}

}
